#ifndef XG_HTTPSERVER_CPP
#define XG_HTTPSERVER_CPP
////////////////////////////////////////////////////////
#include <thread>
#include "../HttpServer.h"
#include "../HttpStream.h"
#include "../HttpResponse.h"
#include "../../dbx/RedisConnect.h"
#include "../../openssl/SSLSocket.h"

#ifndef HTTP_GZIP_EXFMT
#define HTTP_GZIP_EXFMT		".gzip"
#endif

class SSLWorkItem : public WorkItem
{
public:
	int errcode;
	SOCKET sock;
	string addr;
	time_t utime;
	sp<SSLSocket> dest;

public:
	int init()
	{
		static SSLContext* context = SSLContext::GetGlobalContext();

		dest = newsp<SSLSocket>();
		dest->setCloseFlag(false);

		if (dest->init(sock, context))
		{
			utime = time(NULL);
			
			return XG_OK;
		}

		return XG_SYSERR;
	}
	void run()
	{
		if (errcode == XG_OK || errcode == XG_TIMEOUT)
		{
			if (ServerSocketAttach(sock, addr.c_str()))
			{
				utime = time(NULL);
			}
			else
			{
				HttpServer::ReleaseConnect(sock, addr.c_str());
			}
		}
		else
		{
			HttpServer::ReleaseConnect(sock, addr.c_str());
		}
	}
	int accept()
	{
		int res = dest->accept();

		if (res > 0)
		{
			LogTrace(eINF, "connect[%s] success", addr.c_str());
		}

		return res;
	}
	bool runnable()
	{
		errcode = accept();

		if (errcode < 0)
		{
			if (utime + 3 < time(NULL)) errcode = XG_NETERR;
		}
		else
		{
			errcode = XG_OK;
		}

		return true;
	}
	SSLWorkItem(SOCKET sock, const string& addr)
	{
		this->errcode = XG_ERROR;
		this->utime = time(NULL);
		this->addr = addr;
		this->sock = sock;
	}
};

class LoadCgiMapItem : public WorkItem
{
private:
	string key;

public:
	void run()
	{
		HttpServer::Instance()->getCgiMapData(key);
	}
	LoadCgiMapItem(const string& key)
	{
		this->key = key;
	}
};

int CheckHost::check(int host, int maxcnt)
{
	time_t now = time(NULL);
	time_t etime = now - 60;

	if (hostmap.size() > 100000)
	{
		auto it = hostmap.begin();

		while (it != hostmap.end())
		{
			if (it->second.ctime < etime)
			{
				hostmap.erase(it++);
			}
			else
			{
				++it;
			}
		}
	}

	Counter& cnt = hostmap[host];

	if (cnt.ctime < etime)
	{
		cnt.ctime = now;
		cnt.num = 1;

		return 0;
	}

	if (++cnt.num <= maxcnt) return 0;

	cnt.ctime = now + 300 + rand() % 300 + stdx::minval(cnt.num, 300);

	return cnt.ctime - now;
}
	
int HttpServer::getPort()
{
	if (memsz < 0) getShareData();

	return port;
}
string HttpServer::getHost()
{
	if (memsz < 0) getShareData();

	return host == HOST_IP ? LOCAL_IP : host.c_str();
}
void HttpServer::removeCgiAccess(const string& url)
{
	accessmap.remove(CgiMapData::GetKey(url));
}
int HttpServer::getCgiAccess(const string& url) const
{
	int access = CGI_PRIVATE;

	accessmap.get(CgiMapData::GetKey(url), access);

	return access;
}
void HttpServer::setCgiAccess(const string& url, int access)
{
	string key = CgiMapData::GetKey(url);

	accessmap.set(key, access);

	cgimap.update(key, [&](CgiMapData& item){
		item.access = access;
	});
}
string HttpServer::getCgiExtdata(const string& url) const
{
	string extdata;

	cgiextmap.get(CgiMapData::GetKey(url), extdata);

	return extdata;
}
void HttpServer::setCgiExtdata(const string& url, const string& extdata)
{
	string key = CgiMapData::GetKey(url);

	cgiextmap.set(key, extdata);

	cgimap.update(key, [&](CgiMapData& item){
		item.extdata = extdata;
	});
}
map<string, tuple<string, string, string>> HttpServer::getCgiDocMap() const
{
	map<string, tuple<string, string, string>> docmap;

	cgidocmap.get(docmap);

	return std::move(docmap);
}
tuple<string, string, string> HttpServer::getCgiDoc(const string& url) const
{
	tuple<string, string, string> doc;

	cgidocmap.get(CgiMapData::GetKey(url), doc);

	return doc;
}
void HttpServer::setCgiDoc(const string& url, const string& reqdoc, const string& rspdoc, const string& remark)
{
	string key = CgiMapData::GetKey(url);

	cgidocmap.set(key, make_tuple(reqdoc, rspdoc, remark));
}
void HttpServer::setLogCallback()
{
	static int idx;

	idx = 0;

	LogThread::Instance()->callback([&](const string& msg){
		if (sem.wait())
		{
			int len = mq.push(msg.c_str(), msg.length());

			sem.release();

			if (len < 0 && msg.length() < 0xFF)
			{
				Sleep(10);

				if (sem.wait())
				{
					len = mq.push(msg.c_str(), msg.length());

					sem.release();
				}

				if (len < 0 && ++idx > 100) LogThread::Instance()->callback(nullptr);
			}
		}
	});
}
bool HttpServer::loadSystem(bool updated)
{
	if (updated) CHECK_FALSE_RETURN(cfg.reload());

	cfg.getVariable("ALLOW_ORIGIN", trustorigin);
	cfg.getVariable("ROUTE_SWITCH", routeswitch);
	cfg.getVariable("REQUEST_MAXSIZE", reqmaxsz);
	cfg.getVariable("CONNECT_TIMEOUT", timeout);
	cfg.getVariable("CONNECT_REQUEST_MAXTIMES", requestimes);

	if (requestimes <= 0) requestimes = HTTP_REQUEST_MAXTIMES;
	if (reqmaxsz <= 0) reqmaxsz = HTTP_REQDATA_MAXSIZE;
	if (timeout <= 0) timeout = 60;

	stdx::tolower(trustorigin = stdx::trim(trustorigin, " \r\n\t|"));

	if (trustorigin.length() > 0)
	{
		trustorigin = "|" + trustorigin + "|";
		trustorigin = stdx::replace(trustorigin, "|http://", "|");
		trustorigin = stdx::replace(trustorigin, "|https://", "|");
	}

	cachefile.clear();
	pathmap.clear();
	cfgmap.clear();

	if (cgimap.size() > 0)
	{
		cgimap.clear();

		sleep(1);
	}

	int level = 0;
	int maxsz = 8;
	string mimepath;
	ConfigFile mimecfg;
	vector<string> vec;

	cfg.getVariable("THREAD_SIZE", maxsz);
	cfg.getVariable("LOGFILE_LEVEL", level);

	if (maxsz < 1) maxsz = 1;

	if (level >= 0) LogThread::Instance()->setLevel(level);

	if (maxsz != TaskQueue::Instance()->getThreads()) TaskQueue::Instance()->start(maxsz);

	if (cfg.getVariable("MIME_CONFIGFILE_PATH", mimepath))
	{
		if (mimepath.length() > 0 && mimecfg.open(mimepath)) mimecfg.getMapData(mimemap);
	}

	HttpSettingItem::GetCommonFrame(true);

#ifdef _MSC_VER
	stdx::FindFile(vec, path, "*.dll");
#else
	stdx::FindFile(vec, path, "*.so");
#endif
	std::sort(vec.begin(), vec.end(), [](const string& a, const string& b){
		size_t m = a.find("/plugin/");
		size_t n = b.find("/plugin/");
		
		if (m == string::npos && n == string::npos) return a < b;

		if (m == string::npos) return false;

		if (n == string::npos) return true;

		return a < b;
	});

	for (auto& item : vec)
	{
		string url = item.substr(path.length());

		if (memcmp(url.c_str(), "dat/", 4) == 0) continue;

		if (addCgiData(url, item))
		{
			if (url.length() > 0)
			{
				LogTrace(eINF, "auto load[%s][%s] success", url.c_str(), item.c_str());
			}
		}
		else
		{
			LogTrace(eERR, "auto load[%s][%s] failed", url.c_str(), item.c_str());
		}
	}

	map<string, string> cgimap;

	cfg.getMapData(cgimap);

	for (auto& item : cgimap)
	{
		string key = CgiMapData::GetKey(item.first);

		cfgmap[key] = item.second;

		if (key.find("dir@") == 0)
		{
			pathmap[key.substr(4)] = item.second;
		}
		else if (key.find("cgi@") == 0 || key.find("exe@") == 0)
		{
			stdx::async(newsp<LoadCgiMapItem>(key.substr(4)));
		}
	}

	return true;
}
bool HttpServer::loadConfig(const string& filepath)
{
	CHECK_FALSE_RETURN(cfg.init(filepath));

	cfg.getVariable("ID", id);
	cfg.getVariable("NAME", name);
	cfg.getVariable("PATH", path);
	cfg.getVariable("PORT", port);
	cfg.getVariable("HOST", host);

	if (id < 1) id = 1;
	if (host.empty()) host = HOST_IP;
	if (name.empty()) name = Process::GetAppname();
	if (path.empty()) path = Process::GetCurrentDirectory();
	if (name.length() >= MAX_PATH) name = name.substr(0, MAX_PATH - 1);
	if (path.length() >= MAX_PATH) path = path.substr(0, MAX_PATH - 1);

	bool flag = cfg.getVariable("SEQUENCE_HEAD", sequencehead);

	if (sequencehead.empty()) sequencehead = "001";

	CHECK_FALSE_RETURN(IsNumberString(sequencehead.c_str()));

	path += "/";
	path = stdx::replace(path, "\\", "/");
	path = stdx::replace(path, "//", "/");

	CHECK_FALSE_RETURN(Process::SetEnv("HTTP_ROOT_PATH", path));
	CHECK_FALSE_RETURN(Process::SetCurrentDirectory(path));

	int val = stdx::atoi(sequencehead.c_str());
	
	CHECK_FALSE_RETURN(val++ >= 0);

	size_t len = sequencehead.length();
	
	if ((sequencehead = stdx::str(val)).length() > len) sequencehead = "0";

	sequencehead = stdx::fill(sequencehead, len, true, '0');

	if (flag) CHECK_FALSE_RETURN(cfg.setVariable("SEQUENCE_HEAD", sequencehead));

	sequencehead = stdx::str(id) + sequencehead;

	return cfg.save();
}
void HttpServer::close()
{
	TaskQueue::Instance()->stop();
	LogThread::Instance()->wait();
	cgimap.clear();
}
bool HttpServer::initSystem(bool destroyed)
{
	static DllFile dll;
	static HttpProcessBase* cgi = NULL;
	static CREATE_HTTP_CGI_FUNC crtfunc = NULL;
	static DESTROY_HTTP_CGI_FUNC dsyfunc = NULL;

	if (destroyed)
	{
		if (cgi)
		{
			dsyfunc(cgi);
			cgi = NULL;
		}
		
		return true;
	}

	if (path.empty()) path = stdx::translate("$SOURCE_HOME/webapp/");

	if (crtfunc == NULL || dsyfunc == NULL)
	{
		string dllpath;
		string filename = path + "etc/plugin/bin/InitSystem";

#ifdef _MSC_VER
		dllpath = filename + ".dll";
#else
		dllpath = filename + ".so";
#endif
		CHECK_FALSE_RETURN(path::type(dllpath) == eFILE && dll.open(dllpath));

		CHECK_FALSE_RETURN(dll.read(crtfunc, "CreateHttpProcessObject") && dll.read(dsyfunc, "DestroyHttpProcessObject"));
	}
	
	if (cgi) dsyfunc(cgi);

	CHECK_FALSE_RETURN(cgi = crtfunc());

	cgidata.flag = CgiMapData::CGI_FLAG;
	cgidata.destroy_cgi = dsyfunc;
	cgidata.create_cgi = crtfunc;

	return cgi->doWork(NULL, NULL) >= 0;
}

static int ProcessRequestNoException(SOCKET conn, stConnectData* data)
{
	CATCH_EXCEPTION({
		return HttpServer::ProcessRequest(conn, data);
	});

	return XG_SYSERR;
}

static int ProcessConnectClosedNoException(SOCKET conn, stConnectData* data)
{
	CATCH_EXCEPTION({
		return HttpServer::ProcessConnectClosed(conn, data);
	});

	return XG_SYSERR;
}

static int ProcessConnectNoException(SOCKET conn, stConnectData* data, const char* host, int port)
{
	CATCH_EXCEPTION({
		return HttpServer::ProcessConnect(conn, data, host, port);
	});

	return XG_SYSERR;
}

bool HttpServer::init(const string& filepath)
{
	close();

	ServerSocketSetLockFunction(Lock, Unlock);
	ServerSocketSetConnectFunction(ProcessConnectNoException);
	ServerSocketSetProcessFunction(ProcessRequestNoException);
	ServerSocketSetConnectClosedFunction(ProcessConnectClosedNoException);

	string prodpath = Process::GetEnv("PRODUCT_HOME");

	if (prodpath.empty())
	{
		string home = Process::GetEnv("SOURCE_HOME");

#ifdef XG_LINUX
		Process::SetEnv("PRODUCT_HOME", home + "/product");
#else
		Process::SetEnv("PRODUCT_HOME", home + "/product/win");
#endif		
	}

	CHECK_FALSE_RETURN(loadConfig(filepath));

	int sz = 0;
	string logpath;

	cfg.getVariable("LOGFILE_PATH", logpath);

	if (logpath.empty()) logpath = path + "log";

	if (cfg.getVariable("LOGFILE_MAXSIZE", sz)) sz *= 1024;

	if (sz < 1024) sz = 10 * 1024 * 1024;

	LogThread::Instance()->init(logpath, sz);

	if (port == 0)
	{
		srand(time(NULL) + clock());

		string ip = host == HOST_IP ? LOCAL_IP : host;

		for (int i = 0; i < 10; i++)
		{
			if (port == 0)
			{
				string msg = stdx::format("%d:%s:%s", id, name.c_str(), stdx::GetProcessExePath());

				port = 10000 + MD5GetUINT32(msg.c_str(), msg.length()) % 30000;
			}
			else
			{
				port = stdx::random(10000, 30000);
			}

			if (Socket().connect(ip.c_str(), port, 100))
			{
				LogTrace(eIMP, "random port[%d] check failed", port);
			}
			else
			{
				LogTrace(eINF, "random port[%d] check success", port);

				break;
			}
		}
	}

	CHECK_FALSE_RETURN(port > 0 && host.length() > 0);

	cfg.getVariable("SSL_PORT", sslport);
	cfg.getVariable("SSL_HOST", sslhost);

	if (sslport > 0)
	{
		if (sslhost.empty()) sslhost = host;

		string keyfile;
		string certfile;
		string chipherlst;

		cfg.getVariable("CIPHER_LIST", chipherlst);
		cfg.getVariable("CERT_FILEPATH", certfile);
		cfg.getVariable("PRIKEY_FILEPATH", keyfile);
		
		SSLContext* context = SSLContext::GetGlobalContext();

		if (context->setCertificate(certfile))
		{
			LogTrace(eIMP, "load certificate[%s] success", certfile.c_str());
		}
		else
		{
			LogTrace(eERR, "load certificate[%s] failed", certfile.c_str());

			sslport = 0;
		}

		if (context->setCertPrivateKey(keyfile))
		{
			LogTrace(eIMP, "load privatekey[%s] sussess", keyfile.c_str());
		}
		else
		{
			LogTrace(eERR, "load privatekey[%s] failed", keyfile.c_str());

			sslport = 0;
		}

		if (sslport <= 0)
		{
			LogTrace(eIMP, "skip https initialize");
		}
		else
		{		
			context->setClientVerify(false);

			if (chipherlst.length() > 0) context->setCipherList(chipherlst);

			SSL_CTX_load_verify_locations(context->getHandle(), certfile.c_str(), NULL);
			SSL_CTX_set_options(context->getHandle(), SSL_OP_CIPHER_SERVER_PREFERENCE);

#if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_1_0_2
			SSL_CTX_set_alpn_select_cb(context->getHandle(), SSLContext::SelectALPN, NULL);
			SSL_CTX_set_options(context->getHandle(), SSL_OP_SINGLE_ECDH_USE);
			SSL_CTX_set_ecdh_auto(context->getHandle(), 1);
#endif
		}
	}

	initSystem(true);

	const int memsz = HTTP_SERVER_IPC_SHMSIZE * (sizeof(ShareData) / HTTP_SERVER_IPC_SHMSIZE + 1);

	CHECK_FALSE_RETURN(shm.open(GetSharememName()) || shm.create(GetSharememName(), memsz));
	CHECK_FALSE_RETURN(sem.open(GetSemaphoreName()) || sem.create(GetSemaphoreName()));

	string mimepath;
	string dbcfgpath;
	ConfigFile mimecfg;

	cfg.getVariable("MIME_CONFIGFILE_PATH", mimepath);
	cfg.getVariable("DATABASE_CONFIGFILE_PATH", dbcfgpath);

	if (mimepath.length() > 0)
	{
		if (mimecfg.open(mimepath))
		{
			LogTrace(eIMP, "initialize mime success");

			mimecfg.getMapData(mimemap);
		}
		else
		{
			LogTrace(eERR, "initialize mime failed");
		}
	}

	if (dbcfgpath.length() > 0)
	{
		if (initDatabase(dbcfgpath))
		{
			LogTrace(eIMP, "initialize database success");
		}
		else
		{
			LogTrace(eERR, "initialize database failed");
		}
	}

	loadSystem(false);

	if (initSystem(false))
	{
		LogTrace(eIMP, "initialize system success");
	}
	else
	{
		LogTrace(eIMP, "skip system initialize");
	}

	updateCgiData(stdx::EmptyString());

	ShareData* shd = getShareData();

	CHECK_FALSE_RETURN(shd);

	sem.release();

	if (sem.wait())
	{
		mq.create(shd->logdata, sizeof(shd->logdata));

		sem.release();

		setLogCallback();
	}

	memset(shd->data, 0, sizeof(shd->data));
	strcpy(shd->name, name.c_str());
	strcpy(shd->path, path.c_str());
	strcpy(shd->host, host.c_str());
	shd->port = port;

	return true;
}
HttpServer::ShareData* HttpServer::getShareData()
{
	ShareData* shd = NULL;

	if (shm.canUse()) return (ShareData*)(shm.get());

	if (shm.open(GetSharememName()))
	{
		if ((memsz = shm.size()) < 0) return shd;

		shd = (ShareData*)(shm.get());

		if (id == 0)
		{
			name = shd->name;
			path = shd->path;
			host = shd->host;
			port = shd->port;
		}
	}

	return shd;
}
bool HttpServer::initDatabase(const string& filepath)
{
	if (dbconnpool)
	{
		DESTROY_DBCONNECTPOOL_FUNC dsyfunc;

		if (dbconnpooldll.read(dsyfunc, "DestroyDBConnectPool"))
		{
			dsyfunc(dbconnpool);
			dbconnpool = NULL;
		}
	}

	string dllpath;
	ConfigFile dbcfg;
	CREATE_DBCONNECTPOOL_FUNC crtfunc;

	if (dbcfg.open(filepath) && dbcfg.getVariable("DLLPATH", dllpath) && dbconnpooldll.open(dllpath))
	{
		CHECK_FALSE_RETURN(dbconnpooldll.read(crtfunc, "CreateDBConnectPool"));

		CHECK_FALSE_RETURN((dbconnpool = crtfunc()) && dbconnpool->init(dbcfg));

		CHECK_FALSE_RETURN(Process::SetEnv("DATABASE_CONFIGFILE_PATH", filepath));
	}

	int port = 0;
	string host = dbcfg.getVariable("REDIS_HOST");
	string passwd = dbcfg.getVariable("REDIS_PASSWORD");

	dbcfg.getVariable("REDIS_PORT", port);

	if (port > 0 && host.length() > 0)
	{
		Socket sock;
		
		if (sock.connect(host, port))
		{
			RedisConnect::Setup(host, port, passwd);

			sp<RedisConnect> redis = RedisConnect::Instance();

			if (redis)
			{
				LogTrace(eIMP, "initialize redis success");
			}
			else
			{
				LogTrace(eERR, "initialize redis failed");
			}
		}
		else
		{
			LogTrace(eERR, "connect redis failed");
		}
	}

	return true;
}
bool HttpServer::updateModuleFile(const string& src, const string& dest)
{
	DllFile dll;
	GET_HTTP_CGI_PATH_FUNC GetHttpCgiPath = NULL;
	GET_HTTP_CGI_PATH_FUNC GetHttpCgiPathList = NULL;
	string backup = dest + "." + DateTime::GetBizId().substr(0, 14);

	CHECK_FALSE_RETURN(dll.open(dest));

	dll.read(GetHttpCgiPath, "GetHttpCgiPath");
	dll.read(GetHttpCgiPathList, "GetHttpCgiPathList");

	CHECK_FALSE_RETURN(GetHttpCgiPath || GetHttpCgiPathList);

	dll.close();

	Sleep(100);

	path::rename(dest, backup);
	path::rename(src, dest);

	return true;
}

sp<HttpResponse> HttpServer::getLocaleResult(const HttpRequest& request, int timeout)
{
	return request.getResponse(getHost(), getPort(), false, true, timeout);
}
int HttpServer::command(char* cmd)
{
	char ch = *cmd;

	*cmd = 0;

	if (ch == 'k')
	{
		LogTrace(eIMP, "stop process success");

		Process::SetDaemonCommand("exit");

		timeout = 0;
		cmd[1] = 0;
		close();

		ErrorExit(0);
	}

	if (ch == 's')
	{
		if (Process::SetDaemonCommand("restart"))
		{
			LogTrace(eIMP, "restart process success");

			timeout = 0;
			cmd[1] = 0;
			close();

			ErrorExit(0);
		}

		LogTrace(eERR, "restart process failed");

		cmd[1] = 1;
	}

	if (ch == 'r')
	{
		LogThread::Instance()->wait();

		if (loadSystem(true))
		{
			LogTrace(eINF, "reload configure success");

			cmd[1] = 0;
		}
		else
		{
			LogTrace(eERR, "reload configure failed");

			cmd[1] = 1;
		}

		if (cgidata.create_cgi)
		{
			HttpProcessBase* cgi = cgidata.create_cgi();

			if (cgi)
			{
				cgi->doWork(NULL, NULL);
				cgidata.destroy_cgi(cgi);
			}
		}

		updateCgiData(stdx::EmptyString());

		hostmtx.lock();
		hostmap.clear();
		hostmtx.unlock();
	}

	if (ch == 'l')
	{
		cmd[1] = 1;

		if (sem.wait())
		{
			mq.clear();

			sem.release();
		}

		setLogCallback();
	}

	return XG_OK;
}
int HttpServer::updateCgiData(const string& url)
{
	string key;
	string sqlcmd;
	sp<DBConnect> dbconn = getDBConnect();

	if (!dbconn) return XG_SYSERR;

	if ((key = CgiMapData::GetKey(url)).empty())
	{
		stdx::format(sqlcmd, "SELECT PATH,MAXSZ,MAXCNT,ENABLED,HOSTMAXCNT FROM T_XG_CGI");
	}
	else
	{
		stdx::format(sqlcmd, "SELECT PATH,MAXSZ,MAXCNT,ENABLED,HOSTMAXCNT FROM T_XG_CGI WHERE PATH='%s'", key.c_str());
	}

	sp<QueryResult> rs = dbconn->query(sqlcmd);

	if (!rs)
	{
		if (url.empty())
		{
			LogTrace(eERR, "sync cgi configure failed[%s]", dbconn->getErrorString().c_str());
		}
		else
		{
			LogTrace(eERR, "sync cgi[%s] failed[%s]", url.c_str(), dbconn->getErrorString().c_str());
		}

		return XG_SYSERR;
	}
	
	int res = 0;
	int access = 0;
	sp<RowData> row;

	while (row = rs->next())
	{
		string key = row->getString(0);

		cgimap.update(key, [&](CgiMapData& item){
			item.maxsz = row->getInt(1);
			item.maxcnt = row->getInt(2);
			item.access = row->getInt(3);
			item.hostmaxcnt = row->getInt(4);

			access = item.access;
		});

		setCgiAccess(key, access);

		++res;
	}

	if (res == 0 && key.length() > 0)
	{
		cgimap.update(key, [&](CgiMapData& item){
			item.maxcnt = 0;
			item.hostmaxcnt = 0;
			item.access = getCgiAccess(key);
			item.extdata = getCgiExtdata(key);
			item.maxsz = HTTP_REQDATA_MAXSIZE;
		});
	}

	return res;
}
CgiMapData HttpServer::getCgiMapData(const string& url)
{
	string link = stdx::trim(url, HTTP_SPACELIST);
	string key = stdx::tolower(link.c_str());
	CgiMapData item;

	if (key.empty()) return item;
	if (cgimap.get(key, item)) return item;

	string val;
	SpinLocker lk(mtx);
	auto it = cfgmap.find("url@" + key);

	if (it != cfgmap.end())
	{
		item.flag = CgiMapData::URL_FLAG;
		val = it->second;
	}
	else
	{
		it = cfgmap.find("cgi@" + key);
	
		if (it != cfgmap.end())
		{
			item.flag = CgiMapData::CGI_FLAG;
			val = it->second;
		}
		else
		{
			it = cfgmap.find("exe@" + key);
	
			if (it != cfgmap.end())
			{
				item.flag = CgiMapData::EXE_FLAG;
				val = it->second;
			}
		}
	}
	
	if (val.empty())
	{
		string parent = path::parent(link);

		while (parent.length() > 0)
		{
			auto it = pathmap.find(parent);

			if (it != pathmap.end())
			{
				link = it->second + link.substr(parent.length());

				break;
			}

			parent = path::parent(parent);
		}

		if (parent.empty())
		{
			if (url.length() < 5) return CgiMapData::NullObject();
	
			if (url.find("../") != string::npos) return CgiMapData::NullObject();
	
			if (url.find("/pub/") == string::npos && url.find("/css/") == string::npos && memcmp(url.c_str(), "pub/", 4) && memcmp(url.c_str(), "css/", 4)) return CgiMapData::NullObject();
		}

		char ch = link[0];

		item.flag = CgiMapData::URL_FLAG;

		if (ch == '@')
		{
			item.url = link.substr(1);
		}
		else if (ch == '/' || ch == '~' || ch == '\\' || link[1] == ':')
		{
			item.url = link;
		}
		else
		{
			item.url = path + link;
		}

		if (path::type(item.url) < eFILE) return CgiMapData::NullObject();
	}
	else
	{
		size_t pos = val.find('?');

		if (pos != string::npos)
		{
			item.param = val.substr(pos + 1);
			val = val.substr(0, pos);
		}
	
		char ch = val[0];

		item.maxsz = reqmaxsz;

		if (ch == '@')
		{
			item.url = val.substr(1);
		}
		else if (ch == '/' || ch == '~' || ch == '\\' || val[1] == ':')
		{
			item.url = val;
		}
		else
		{
			item.url = path + val;
		}
	}

	if (item.flag == CgiMapData::URL_FLAG)
	{
		const string& filepath = item.url;
		static const size_t GZIP_EXFMT_LEN = strlen(HTTP_GZIP_EXFMT);
	
		if (filepath.length() > GZIP_EXFMT_LEN)
		{
			string key = filepath.c_str() + filepath.length() - GZIP_EXFMT_LEN;
	
			if (stdx::tolower(key) == HTTP_GZIP_EXFMT) item.code = CgiMapData::GZIP_CODE;
		}
	}

	CgiMapData tmp = item;
	string dest = tmp.url;

	if (item.url.find(path) == 0)
	{
		dest = item.url.substr(path.length());
	}

	dest = CgiMapData::GetKey(dest);

	if (cgimap.get(dest, item))
	{
		item.dest = dest;

		if (item.param.empty())
		{
			item.param = tmp.param;
		}
		else
		{
			item.param += "&" + tmp.param;
		}
	}
	else
	{
		string path = key;

		stdx::tolower(path);

		if (IsDynamicLoad())
		{
			LogTrace(eINF, "dynamic load[%s][%s] success", path.c_str(), item.url.c_str());
		}
		else
		{
			if (item.flag == CgiMapData::CGI_FLAG)
			{
				sp<DllFile> dll = newsp<DllFile>();

				if (dll->open(item.url))
				{
					dll->read(item.create_cgi, "CreateHttpProcessObject");
					dll->read(item.destroy_cgi, "DestroyHttpProcessObject");
				}

				if (item.create_cgi == NULL || item.destroy_cgi == NULL) return CgiMapData::NullObject();

				item.dll = dll;
			}

			LogTrace(eINF, "static load[%s][%s] success", path.c_str(), item.url.c_str());
		}
	}

	if (cgimap.size() > 1000000)
	{
		std::thread([&]{
			Sleep(100);

			getShareData()->data[0] = 'r';

			command(getShareData()->data);
		}).detach();
	}

	cgimap.set(key, item);

	updateCgiData(key);

	cgimap.get(key, item);

	return item;
}
int HttpServer::getCgiMap(map<string, CgiMapData>& cgimap)
{
	return this->cgimap.get(cgimap);
}
bool HttpServer::addCgiData(string& url, const string& path)
{
	if (path.empty())
	{
		CgiMapData item(CgiMapData::CGI_FLAG, CgiMapData::NONE_CODE, url);

		item.access = getCgiAccess(url);
		item.extdata = getCgiExtdata(url);
		item.create_cgi = cgidata.create_cgi;
		item.destroy_cgi = cgidata.destroy_cgi;
		cgimap.set(CgiMapData::GetKey(url), item);

		return true;
	}

	vector<string> vec;
	string extname = path::extname(path);
	CgiMapData item(CgiMapData::URL_FLAG, CgiMapData::NONE_CODE, path);

	auto getPath = [&](const char* cgipath){
		if (cgipath == NULL || *cgipath == 0) return XG_OK;

		string key = stdx::trim(cgipath);

		if (key.empty()) return XG_OK;

		if (key == "@null" || key == "@hidden" || key == "@disable")
		{
			url.clear();

			return XG_NOTFOUND;
		}

		url = GetWebAppPath(key.c_str(), path.c_str());

		return XG_OK;
	};
	
	if (stdx::tolower(extname) == "exe")
	{
		char cgipath[64 * 1024];
		
		if (cmdx::RunCommand(stdx::quote(path) + " -c getcgipathlist", cgipath, sizeof(cgipath)) > 0)
		{
			stdx::split(vec, stdx::trim(cgipath), "|");
		}
		
		if (vec.empty())
		{
			if (cmdx::RunCommand(stdx::quote(path) + " -c getcgipath", cgipath, sizeof(cgipath)) < 0) return false;

			if (getPath(cgipath) == XG_NOTFOUND) return true;
		}

		item.flag = CgiMapData::EXE_FLAG;
	}
	else
	{
		sp<DllFile> dll;

		if (IsDynamicLoad())
		{
			dll = newsp<DllFile>();

			if (dll->open(item.url)) item.flag = CgiMapData::CGI_FLAG;
		}
		else
		{
			dll = newsp<DllFile>();

			if (dll->open(item.url))
			{
				dll->read(item.create_cgi, "CreateHttpProcessObject");
				dll->read(item.destroy_cgi, "DestroyHttpProcessObject");

				if (item.create_cgi == NULL || item.destroy_cgi == NULL) return false;

				item.flag = CgiMapData::CGI_FLAG;
				item.dll = dll;
			}
		}

		if (item.flag == CgiMapData::CGI_FLAG)
		{
			GET_HTTP_CGI_PATH_FUNC GetHttpCgiPathList = NULL;

			if (dll->read(GetHttpCgiPathList, "GetHttpCgiPathList"))
			{
				const char* path = GetHttpCgiPathList();

				if (path) stdx::split(vec, stdx::trim(path), "|");
			}

			if (vec.empty())
			{
				GET_HTTP_CGI_PATH_FUNC GetHttpCgiPath = NULL;

				if (dll->read(GetHttpCgiPath, "GetHttpCgiPath"))
				{
					if (getPath(GetHttpCgiPath()) == XG_NOTFOUND) return true;
				}
			}
		}
	}

	if (item.flag == CgiMapData::URL_FLAG)
	{
		const string& filepath = item.url;
		static const size_t GZIP_EXFMT_LEN = strlen(HTTP_GZIP_EXFMT);

		if (filepath.length() > GZIP_EXFMT_LEN)
		{
			string key = filepath.c_str() + filepath.length() - GZIP_EXFMT_LEN;

			if (stdx::tolower(key) == HTTP_GZIP_EXFMT) item.code = CgiMapData::GZIP_CODE;
		}
	}

	if (vec.empty())
	{
		item.access = getCgiAccess(url);
		item.extdata = getCgiExtdata(url);
		cgimap.set(CgiMapData::GetKey(url), item);
	}
	else
	{
		for (string& url : vec)
		{
			url = CgiMapData::GetKey(url);

			if (url.empty()) continue;

			item.access = getCgiAccess(url);
			item.extdata = getCgiExtdata(url);
			cgimap.set(CgiMapData::GetKey(url), item);
			
			LogTrace(eINF, "auto load[%s][%s] success", url.c_str(), path.c_str());
		}

		url.clear();
	}

	return true;
}
string HttpServer::getName()
{
	if (memsz < 0) getShareData();
	
	return name;
}
string HttpServer::getPath()
{
	if (memsz < 0) getShareData();
	
	return path;
}
string HttpServer::getSequence()
{
	char buffer[16];
	static atomic_int idx(0);

	sprintf(buffer, "%08d", ++idx % 100000000);

	return sequencehead + buffer;
}
void HttpServer::removeSession(const string& name)
{
	sessmap.remove(name);
}
sp<Session> HttpServer::getSession(const string& name, int timeout)
{
	sp<HttpSession> session;

	if (sessmap.get(name, session))
	{
		if (!session->isTimeout()) return session;

		sessmap.remove(name);
		session = NULL;
	}

	if (timeout <= 0) return session;

	sessmap.set(name, session = sp<HttpSession>(new HttpSession(name, timeout)));

	LogTrace(eINF, "create session[%s][%d]", name.c_str(), timeout);

	return session;
}

sp<DBConnect> HttpServer::getDBConnect()
{
	return dbconnpool ? dbconnpool->get() : NULL;
}
void HttpServer::disableDBConnect(sp<DBConnect> conn)
{
	if (dbconnpool) dbconnpool->disable(conn);
}
string HttpServer::getMimeType(const string& key) const
{
	auto it = mimemap.find(key);
		
	return it == mimemap.end() ? stdx::EmptyString() : it->second;
}
int HttpServer::getFileContent(const string& path, SmartBuffer& content)
{
	return cachefile.getContent(path, content);
}
int HttpServer::getFileContent(const string& path, SmartBuffer& content, time_t& utime)
{
	return cachefile.getContent(path, content, utime);
}

void HttpServer::loop()
{
	class SessionCheckItem : public WorkItem
	{
		time_t ctime = time(NULL);

		void run()
		{
			time_t now;
			DateTime tmp(time(&now));

			if (tmp.canUse())
			{
				static DateTime date(now);
				static HttpServer* app = HttpServer::Instance();

				if (date.day == tmp.day)
				{
					app->sessmap.clear([now](sp<HttpSession>& session){
						if (session->isTimeout(now))
						{
							LogTrace(eINF, "session[%s] timeout", session->getName().c_str());

							return true;
						}

						return false;
					});
				}
				else
				{
					SpinLocker lk(app->transmtx);
					app->transmap.clear();
					date = tmp;
				}
			}

			LogTrace(eINF, "check session success");
		}
		bool runnable()
		{
			time_t now = time(NULL);

			if (ctime + 60 < now)
			{
				ctime = now;
	
				run();
			}

			return false;
		}
	};

	Process::SetDaemonCommand("restart");
	stdx::async(newsp<SessionCheckItem>());

	int backlog = TaskQueue::Instance()->getThreads() << 3;

	if (sslport > 0)
	{
		std::thread([&](){
			Sleep(100);

			ServerSocketLoop(sslhost.c_str(), sslport, backlog, timeout);

			LogTrace(eERR, "listen host[%s][%d] failed[%s]", sslhost.c_str(), sslport, GetErrorString().c_str());
		}).detach();
	}

	ServerSocketLoop(host.c_str(), port, backlog, timeout);

	Process::SetDaemonCommand("exit");

	LogThread::Instance()->setLogFlag(2);

	LogTrace(eERR, "listen host[%s][%d] failed[%s]", host.c_str(), port, GetErrorString().c_str());
}

int HttpServer::ProcessConnect(SOCKET conn, stConnectData* data, const char* host, int port)
{
	static HttpServer* app = Instance();
	static char* cmd = app->getShareData()->data;
	static u_short* socktimeslist = app->socktimeslist;

	socktimeslist[(u_short)(conn)] = 0;
	SocketSetSendTimeout(conn, SOCKECT_SENDTIMEOUT);
	SocketSetRecvTimeout(conn, SOCKECT_RECVTIMEOUT);

	if (*cmd)
	{
		if (Instance()->isActive()) Instance()->command(cmd);

		return XG_NETCLOSE;
	}

	if (app->sslport == port && app->sslhost == host)
	{
		sp<SSLWorkItem> item = newsp<SSLWorkItem>(conn, data->addr);

		if (item->init() < 0) return XG_SYSERR;

		app->sslconnmap.set(conn, item);

		return stdx::async(item) ? XG_DETACHCONN : XG_SYSBUSY;
	}

	LogTrace(eINF, "connect[%s] success", data->addr);

	return XG_OK;
}

int HttpServer::ReleaseConnect(SOCKET conn, const char* addr)
{
	static HttpServer* app = HttpServer::Instance();

	LogTrace(eINF, "connect[%s] shutdown", addr);

	app->streamap.remove(conn);
	
	if (app->sslconnmap.remove(conn))
	{
		if (stdx::delay(3000, [conn]{
			SocketClose(conn);
		})) return XG_DETACHCONN;
	}

	SocketClose(conn);

	return XG_DETACHCONN;
}

int HttpServer::ProcessConnectClosed(SOCKET conn, stConnectData* data)
{
	return ReleaseConnect(conn, data->addr);
}

void HttpRequestItem::run()
{
	int res = process();
	SOCKET conn = sock->getHandle();

	if (res >= 0)
	{
		if (ServerSocketAttach(conn, addr.c_str()))
		{
			LogTrace(eDBG, "connect[%s] attach", addr.c_str());
		}
		else
		{
			HttpServer::ReleaseConnect(conn, addr.c_str());
		}
	}
	else if (res == XG_DETACHCONN)
	{
		LogTrace(eDBG, "connect[%s] detach", addr.c_str());
	}
	else
	{
		HttpServer::ReleaseConnect(conn, addr.c_str());
	}
}
int HttpRequestItem::process()
{
	int val = XG_SYSERR;
	HttpResponse response;
	const string& url = request.getPath();
	static HttpServer* app = HttpServer::Instance();
	static u_short* socktimeslist = app->socktimeslist;

	if (readed < 0)
	{
		LogTrace(eERR, "connect[%s] process failed[%d]", addr.c_str(), readed);

		return XG_SYSERR;
	}

	if (response.init(&request, sock, addr))
	{
		u_short& num = socktimeslist[(u_short)(sock->getHandle())];

		if (++num >= app->getMaxRequestTimes())
		{
			response.setHeadValue("Connection", "close");

			num = 0;
		}
		else
		{
			response.setHeadValue("Connection", "keep-alive");
			response.setHeadValue("Keep-Alive", "timeout=" + stdx::str(app->getTimeout()));
		}

		val = response.forward(url, addr);

		if (val == XG_NOTFOUND || val == XG_AUTHFAIL)
		{
			sp<HttpResponseItem> rspdata = newsp<HttpResponseItem>(sock, addr);
	
			if (val == XG_NOTFOUND)
			{
				LogTrace(eIMP, "request[%s][%s] resource not found", addr.c_str(), url.c_str());
			
				val = rspdata->init(response.getNotFoundContent());
			}
			else
			{
				LogTrace(eIMP, "request[%s][%s] resource forbidden", addr.c_str(), url.c_str());
			
				val = rspdata->init(response.getRefusedContent());
			}

			if (val > 0) val = stdx::async(rspdata) ? XG_DETACHCONN : XG_SYSBUSY;
		}
	}

	if (val >= 0)
	{
		LogTrace(eINF, "request[%s][%s] process success[%d]", addr.c_str(), url.c_str(), val);
	}
	else if (val == XG_DETACHCONN)
	{
		LogTrace(eDBG, "request[%s][%s] detach connect", addr.c_str(), url.c_str());
	}
	else
	{
		LogTrace(eERR, "request[%s][%s] process failed[%d]", addr.c_str(), url.c_str(), val);
	}

	return val;
}
bool HttpRequestItem::runnable()
{
	int offset = readed;
	static int timeout = HttpServer::Instance()->getTimeout();
	static HttpServer::ShareData* shd = HttpServer::Instance()->getShareData();

	if (buffer.isNull()) return true;

	if (shd->data[0])
	{
		readed = XG_SYSBUSY;

		return true;
	}

	int res = request.init(sock.get(), buffer, readed);

	if (res == XG_TIMEOUT)
	{
		if (readed > offset + SOCKET_TIMEOUT_LIMITSIZE)
		{
			utime = time(NULL);
		}
		else if (utime + timeout < time(NULL))
		{
			readed = XG_TIMEOUT;

			return true;
		}

		return false;
	}

	if (res < 0)
	{
		readed = res;

		return true;
	}

	if (readed > offset + SOCKET_TIMEOUT_LIMITSIZE)
	{
		utime = time(NULL);
	}
	else if (utime + timeout < time(NULL))
	{
		readed = XG_TIMEOUT;

		return true;
	}

	if (readed == buffer.size())
	{
		int addsz = request.getAdditionSize();

		if (addsz > 0)
		{
			if (maxsz == 0)
			{
				const string& url = request.getPath();

				if (url.empty())
				{
					readed = XG_DATAERR;

					return true;
				}

				maxsz = HttpServer::Instance()->getCgiMapData(url).maxsz;
			}

			if (readed + addsz <= maxsz)
			{
				buffer.truncate(readed + addsz);

				return false;
			}
		}
	}

	return true;
}

void HttpResponseItem::run()
{
	SOCKET conn = sock->getHandle();

	if (writed >= 0)
	{
		LogTrace(eINF, "connect[%s] response success[%d]", addr.c_str(), writed);

		if (ServerSocketAttach(conn, addr.c_str()))
		{
			LogTrace(eDBG, "connect[%s] attach", addr.c_str());
		}
		else
		{
			HttpServer::ReleaseConnect(conn, addr.c_str());
		}
	}
	else
	{
		LogTrace(eERR, "connect[%s] response failed[%d]", addr.c_str(), writed);

		HttpServer::ReleaseConnect(conn, addr.c_str());
	}
}

bool HttpResponseItem::runnable()
{
	int len = data.size();
	int val = sock->write(data.str() + writed, len - writed, false);

	if (val < 0)
	{
		writed = val;
	
		return true;
	}

	if (val > SOCKET_TIMEOUT_LIMITSIZE)
	{
		utime = time(NULL);
	}
	else
	{
		static int timeout = HttpServer::Instance()->getTimeout();

		if (utime + timeout < time(NULL))
		{
			writed = XG_TIMEOUT;

			return true;
		}
	}

	if ((writed += val) >= len)
	{
		if (filelen <= 0) return true;

		if (len != SOCKECT_FRAMESIZE) data.malloc(len = SOCKECT_FRAMESIZE);

		if ((val = file->read(data.str(), len)) <= 0)
		{
			writed = XG_IOERR;

			return true;
		}

		if ((filelen -= val) < 0)
		{
			writed = XG_IOERR;

			return true;
		}

		if (val < len) data.truncate(val);

		writed = 0;
	}

	return false;
}
int HttpResponseItem::init(SmartBuffer data)
{
	int len = data.size();

	if (len <= 0) return XG_SYSERR;

	this->data = data;

	return len;
}
int HttpResponseItem::init(HttpResponse* response, const void* msg, int len)
{
	if (len < 0)
	{
		file = newsp<XFile>();

		if (file->open((char*)(msg), eREAD)) filelen = file->size();

		if (filelen < 0) return XG_NOTFOUND;

		response->setHeadValue("Content-Length", stdx::str(filelen));

		data = response->getHeadString();
	}
	else
	{
		response->setHeadValue("Content-Length", stdx::str(len));

		string head = response->getHeadString();
		int hdrsz = head.length();

		memcpy(data.malloc(hdrsz + len), head.c_str(), hdrsz);

		if (len > 0) memcpy(data.str() + hdrsz, msg, len);

		writed = sock->write(data.str(), data.size(), false);

		if (writed >= data.size()) return len;
	}

	return writed;
}

void HttpServer::Lock()
{
	static SpinMutex& mtx = Instance()->svrmtx;

	mtx.lock();
}
void HttpServer::Unlock()
{
	static SpinMutex& mtx = Instance()->svrmtx;

	mtx.unlock();
}
bool HttpServer::IsDynamicLoad()
{
	static const char* cmd = Process::GetCmdParam("-d");

	return cmd ? true : false;
}
HttpServer* HttpServer::Instance()
{
	XG_DEFINE_GLOBAL_VARIABLE(HttpServer)
}
const char* HttpServer::GetSharememName()
{
	static string name = stdx::format("%s:webapp:%s", GetCurrentUser(), Instance()->getClassName());

	return name.c_str();
}
const char* HttpServer::GetSemaphoreName()
{
	static string name = stdx::format("%s:semaphore", GetSharememName());

	return name.c_str();
}
string HttpServer::GetWebAppPath(const char* path, const char* filename)
{
	if (path == NULL || *path == 0) path = "${filename}";

	string res = CgiMapData::GetKey(path);

	if (strstr(path, "$classname") || strstr(path, "$(classname)") || strstr(path, "${classname}"))
	{
		size_t pos = 0;
		string name = path::name(filename);

		if ((pos = name.rfind('.')) != string::npos) name = name.substr(0, pos);
		
		res = stdx::replace(res, "$classname", name);
		res = stdx::replace(res, "$(classname)", name);
		res = stdx::replace(res, "${classname}", name);
	}

	if (strstr(path, "$filename") || strstr(path, "$(filename)") || strstr(path, "${filename}"))
	{
		size_t pos = 0;
		string name = path::name(filename);

		if ((pos = name.rfind('.')) != string::npos) name = name.substr(0, pos);
		
		res = stdx::replace(res, "$filename", name);
		res = stdx::replace(res, "$(filename)", name);
		res = stdx::replace(res, "${filename}", name);
	}

	if (strstr(path, "$package") || strstr(path, "$(package)") || strstr(path, "${package}"))
	{
		size_t pos = 0;
		string path = path::parent(filename);

		res = stdx::replace(res, "$package", path);
		res = stdx::replace(res, "$(package)", path);
		res = stdx::replace(res, "${package}", path);
	}

	return res;
}
int HttpServer::ProcessRequest(SOCKET conn, stConnectData* data)
{
	sp<Socket> sock;
	sp<HttpStream> item;
	static char tag[] = "PRI * HTTP";
	static HttpServer* app = Instance();

	if (app->streamap.get(conn, item)) return stdx::async(item) ? XG_DETACHCONN : XG_SYSBUSY;

	if (app->sslport > 0)
	{
		sp<SSLWorkItem> item;

		if (app->sslconnmap.get(conn, item))
		{
			if (item->errcode < 0) return stdx::async(item) ? XG_DETACHCONN : XG_SYSBUSY;

			sock = item->dest;
		}
	}

	if (!sock)
	{
		sock = newsp<Socket>();
		sock->init(conn);
		sock->setCloseFlag(false);
	}

	char buffer[sizeof(tag) - 1];
	int readed = sock->read(buffer, sizeof(buffer), false);

	if (readed < 0) return readed;

	if (readed < sizeof(buffer))
	{
		int len = sock->read(buffer + readed, sizeof(buffer) - readed, false);

		if (len < 0) return len;

		readed += len;

		if (readed < sizeof(buffer)) return XG_NETERR;
	}

	if (memcmp(tag, buffer, sizeof(buffer)) == 0)
	{
		item = newsp<HttpStream>(sock, data->addr);

		if (readed > sizeof(item->buffer)) return XG_SYSERR;

		app->streamap.set(conn, item);

		memcpy(item->buffer, buffer, item->readed = readed);

		return stdx::async(item) ? XG_DETACHCONN : XG_SYSBUSY;
	}
	else
	{
		sp<HttpRequestItem> item = newsp<HttpRequestItem>(sock, data->addr);

		if (readed > item->buffer.size()) return XG_SYSERR;

		memcpy(item->buffer.str(), buffer, item->readed = readed);

		return stdx::async(item) ? XG_DETACHCONN : XG_SYSBUSY;
	}

	return XG_SYSERR;
}

////////////////////////////////////////////////////////
#endif